/**
 * @author Jerome Monceaux & Sylvere Billout
 * @version 1.0
 * 
 * Send ioio events to all subscribed modules
 **/
package org.hh.common;

import ioio.lib.api.DigitalOutput;
import ioio.lib.api.IOIO;
import ioio.lib.api.IOIOFactory;
import ioio.lib.api.exception.ConnectionLostException;


import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;


public class IOIOManager extends HHObject{

	/** Is the phone connected to the board */
	private boolean fIsConnected = false;
	
	/** Is the initialization already done. */
	private boolean fIsInit = false;
	
	/** Is it a pure simulation */
	public boolean fIsSimulation = false;
	
	/** Cycle time (in millisecond) **/
	private final static int UPDATE_TIME = 20; 
	private int fElapsedTime; // process duration without sleep
	private int fLastOverflowedTime = 0;
	
	/** thread to update the ioio's devices */ 
	private volatile IOIOThread ioio_thread_;

	/** Name of the preference file **/
	public static final String PREFS_NAME = "scalpel";
	
	
  public IOIOManager(String name) {
  	super(name);
	}
  
  public void startLoop(){
  	ioio_thread_ = new IOIOThread();
		ioio_thread_.start();
  }
  
  public void stopLoop(){
    if(ioio_thread_ != null){
      Thread moribund = ioio_thread_;
      ioio_thread_ = null;
      moribund.interrupt();
  		try {
  			moribund.join();
  		} catch (InterruptedException e) {
  		}
    }
  }
  
 
	/** onDraw notification of registered module */
	public void onDraw(Canvas canvas, Paint paint){
		canvas.save();
		final int connectedPosY = 473; // position in the screen
		final int connectedPosX = 685;
		String statusString = "IOIO ";
		if( !fIsInit )
  	{
  		paint.setColor(Color.GRAY);
  		canvas.drawCircle(connectedPosX, connectedPosY, 3, paint);
  		statusString += "not init";
    	canvas.drawText(statusString, connectedPosX+10, connectedPosY+5, paint);
  		return;
  	}
		
  	if( !fIsConnected ){
  		paint.setColor(Color.RED);
  		statusString += "not connected";
  	}
		else{
			paint.setColor(Color.GREEN);
			statusString += "ok";
		}
    
  	canvas.drawCircle(connectedPosX, connectedPosY, 3, paint);
  	canvas.drawText(statusString, connectedPosX+10, connectedPosY+5, paint);
		
  	int e = 0;
  	synchronized(this){
  		e = fElapsedTime;
  	}
  	if (e <= UPDATE_TIME)
			paint.setColor(Color.GREEN);
		else
		{
			paint.setColor(Color.RED);
			fLastOverflowedTime = e;
		}
		
		canvas.drawText("Update Time : " + Integer.toString(UPDATE_TIME)
				//+" Time cycle : " + Integer.toString(e)
				+" Last overflow : " + Integer.toString(fLastOverflowedTime)
				+" Time load : " + Integer.toString((int)(100*(float)e / UPDATE_TIME)) + "%"
				, 5, connectedPosY, paint);
		
		canvas.restore();
	}
	
	/** onInitView notification of registered module */
	public void onInitView(Context context) {
		// read preference : start with simulation mode or not ? 
		SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, 0);
	  fIsSimulation = settings.getBoolean("simulation", true);
	}

	
	/**
	 * An abstract class, which facilitates a thread dedicated for IOIO
	 * communication.
	 */
	protected class IOIOThread extends Thread {
		/** Subclasses should use this field for controlling the IOIO. */
		protected IOIO ioio_ = null;
		private boolean abort_ = false;
		private DigitalOutput led_;
		
		private long fLastTime;
		private long fCurrentTime;
			/**
		 * Subclasses should override this method for performing operations to
		 * be done once as soon as IOIO communication is established. Typically,
		 * this will include opening pins and modules using the openXXX()
		 * methods of the {@link #ioio_} field.
		 */
		synchronized protected void setup( ) throws ConnectionLostException {
			if( !fIsSimulation && ioio_ != null){
				led_ = ioio_.openDigitalOutput(IOIO.LED_PIN, false);
			}
			
			// notify subscribers 
			setChanged();
			notifyObservers(new Object[]{"initIOIO", new Object[]{fIsSimulation, ioio_}});

			
			fLastTime = System.currentTimeMillis();
			
			if( !fIsSimulation && ioio_ != null){
				led_.write(false); // turn the led on
			}
			fIsInit = true;		
		}

		/**
		 * Subclasses should override this method for performing operations to
		 * be done repetitively as long as IOIO communication persists.
		 * Typically, this will be the main logic of the application, processing
		 * inputs and producing outputs.
		 */
		synchronized protected void loop() throws ConnectionLostException {

			try {
				
				// notify subscribers
				setChanged();
				notifyObservers(new Object[]{"updateIOIO", null});
				 
				int elapsed = 0;
				fCurrentTime = System.currentTimeMillis();
				elapsed = (int) (fCurrentTime-fLastTime);
				
				synchronized(this){
					fElapsedTime = elapsed;
				}

				if(elapsed < UPDATE_TIME/2)
					Thread.sleep(UPDATE_TIME/4, 0); // Sleep, but not too much, OS could forget to wake us up.
				
				while(elapsed < UPDATE_TIME){ // The best way to sleep... but the most CPU expensive
					fCurrentTime = System.currentTimeMillis();
					elapsed = (int) (fCurrentTime-fLastTime);
				}
								
				fCurrentTime = System.currentTimeMillis();
				elapsed = (int) (fCurrentTime-fLastTime);
				fLastTime = fCurrentTime;
				
			} catch (InterruptedException e) {
				ioio_.disconnect();
			}
		}

		/** Not relevant to subclasses. */
		@SuppressWarnings("finally")
		@Override
		public final void run() {
			super.run();
			
				while (Thread.currentThread() == ioio_thread_) {
					// Set the highest priority possible. We need to keep the cycle duration constant 
					Thread.currentThread().setPriority(MAX_PRIORITY);
					try {
						synchronized (this) {
							if (abort_) {
								break;
							}
							ioio_ = IOIOFactory.create();
						}
						if(!fIsSimulation){
							ioio_.waitForConnect(); // This is a blocking method waiting for the board connection. 
							// No way to switch the simulation automatically. 
							fIsConnected = true;
						}else{
							fIsConnected = false;
						}
						setup();
						
						while (Thread.currentThread() == ioio_thread_) {
							loop(); // call the main loop
						}
					} catch (ConnectionLostException e) {
						if (abort_) {
							break;
						}
					} catch (Exception e) {
						Log.e("IOIOThread",
								"Unexpected exception caught", e);
						ioio_.disconnect();
						break;
					} finally {
						// If an error occurs it would be nice to switch to simulation mode but...
						break;
					}
				}
			
		}

		/** Not relevant to subclasses. */
		public synchronized final void abort() {
			abort_ = true;
			if (ioio_ != null) {
				ioio_.disconnect();
			}
		}
	}
	

	/** return true if the board is correctly connected */
	public boolean isConnected() {
		return fIsConnected;
	}

	
	/** return true if the setup of the control thread of the board has been done */
	public boolean isInit() {
		return fIsInit;
	}
}
